﻿
	public class DockLayout : Layout
	{
		public enum Area
		{
			Top,
			Left,
			Right,
			Bottom
		}
				
		private class Item
		{
			public ctor(ILayoutable content)
			{
				this.Content = content;
			}
			
			public ILayoutable Content;
			public SizeHint SizeHint;
			public Size Size;
			public Point Position;
			public Area Area;
		}
				
		/// If null then no size hint has been computed so far
		private SizeHint sizeHint;
		private Size size = new Size(0,0);
		private Point position = new Point(0,0);
		private Item left;
		private Item right;
		private Item top;
		private Item bottom;
		private Item center;

		public ctor()
		{
		}

		public ILayoutable LeftContent
		{
			get
			{
				return this.left.Content;
			}
			set
			{
				this.left = new Item(value);
				this.PostSetContent(this.left);
			}
		}
		
		public ILayoutable RightContent
		{
			get
			{
				return this.right.Content;
			}
			set
			{
				this.right = new Item(value);
				this.PostSetContent(this.right);
			}
		}

		public ILayoutable TopContent
		{
			get
			{
				return this.top.Content;
			}
			set
			{
				this.top = new Item(value);
				this.PostSetContent(this.top);
			}
		}

		public ILayoutable BottomContent
		{
			get
			{
				return this.bottom.Content;
			}
			set
			{
				this.bottom = new Item(value);
				this.PostSetContent(this.bottom);
			}
		}

		public ILayoutable CenterContent
		{
			get
			{
				return this.center.Content;
			}
			set
			{
				this.center = new Item(value);
				this.PostSetContent(this.center);
			}
		}

		private void PostSetContent(Item item)
		{
			if ( item.Content is Layout )
			{
				var layout = item.Content as Layout;
				if ( this.Widget != null )
				{
					layout.Widget = this.Widget;
					// The layout has already layouted? -> repeat
					if ( this.size != null )
					{
						// Trigger a re-layout
						this.Size = this.Size;
					}
				}
				else
				{
					this.sizeHint = null;
				}
			}
			else
			{
				var widget = item.Content as Widget;
				// Layout is added to a widget already?
				if ( this.Widget != null )
				{
					// Append the new widget in the DOM
					this.Widget.GetDOM().AppendChild( widget.GetDOM() );
				
					// The parent widget is already visible in the browser
					if (this.Widget.IsInTree)
					{
						// Signal that the new widget is visible in the browswer, too
						widget.OnTreeJoin();
						// The layout has already layouted? -> repeat
						if ( this.size != null )
						{
							// Trigger a re-layout
							this.Size = this.Size;
						}
					}
				}
				else
				{
					this.sizeHint = null;
				}
			}
		}
		
		public override list<ILayoutable> Content
		{
			get
			{
				var list<ILayoutable> lst = [];
				if ( this.left != null )
					lst.Push( this.left.Content );
				if ( this.right != null )
					lst.Push( this.right.Content );
				if ( this.top != null )
					lst.Push( this.top.Content );
				if ( this.bottom != null )
					lst.Push( this.bottom.Content );
				if ( this.center != null )
					lst.Push( this.center.Content );
					
				return lst;
			}
		}
				
		public override SizeHint GetWidthHint()
		{
			if ( this.sizeHint != null )
			{
				return this.sizeHint;
			}

			this.sizeHint = new SizeHint();
			
			foreach( var item in this.Content )
			{
				item.SizeHint = item.Content.GetWidthHint();
				if ( item.SizeHint.ExpandsVertically ) { this.sizeHint.ExpandsVertically = true; }
				if ( item.SizeHint.ExpandsHorizontally ) { this.sizeHint.ExpandsHorizontally = true; }
			}
			
			Size minSize = new Size(0,0);
			Size maxSize = new Size(100000,100000);
			Size prefSize = new Size(0,0);
			
			if ( this.left != null )
			{
				minSize.Width += this.left.SizeHint.MinimumSize.Width;
				maxSize.Width += this.left.SizeHint.MaximumSize.Width;
				prefSize.Width += this.left.SizeHint.PreferredSize.Width;
			}
			if ( this.center != null )
			{
				minSize.Width += this.center.SizeHint.MinimumSize.Width;
				maxSize.Width += this.center.SizeHint.MaximumSize.Width;
				prefSize.Width += this.center.SizeHint.PreferredSize.Width;
			}
			if ( this.right != null )
			{
				minSize.Width += this.right.SizeHint.MinimumSize.Width;
				maxSize.Width += this.right.SizeHint.MaximumSize.Width;
				prefSize.Width += this.right.SizeHint.PreferredSize.Width;
			}
			
			if ( this.top != null )
			{
				minSize.Width = Math.Max(minSize.Width, this.top.SizeHint.MinimumSize.Width;
				maxSize.Width = Math.Min(maxSize.Width, this.top.SizeHint.MaximumSize.Width;
				prefSize.Width = Math.Max(prefSize.Width, this.top.SizeHint.PreferredSize.Width;
			}
			if ( this.bottom != null )
			{
				minSize.Width = Math.Max(minSize.Width, this.bottom.SizeHint.MinimumSize.Width;
				maxSize.Width = Math.Min(maxSize.Width, this.bottom.SizeHint.MaximumSize.Width;
				prefSize.Width = Math.Max(prefSize.Width, this.bottom.SizeHint.PreferredSize.Width;
			}
			
			this.sizeHint.MinimumSize = minSize;
			this.sizeHint.MaximumSize = maxSize;
			this.sizeHint.PreferredSize = prefSize;
			
			return this.sizeHint;
		}
		
		public override SizeHint GetHeightHint( num width )
		{
			if ( this.sizeHint == null )
			{
				this.GetWidthHint();
			}
			
			Size minSize = new Size(this.sizeHint.MinimumSize.Width,0);
			Size maxSize = new Size(this.sizeHint.MaximumSize.Width,100000);
			Size prefSize = new Size(this.sizeHint.PreferredSize.Width,0);
			if ( this.top != null )
			{
				this.top.SizeHint = this.top.Content.GetHeightHint(width);
				minSize.Height += this.top.SizeHint.MinimumSize.Height;
				maxSize.Height += this.top.SizeHint.MaximumSize.Height;
				prefSize.Height += this.top.SizeHint.PreferredSize.Height;
			}
			if ( this.bottom != null )
			{
				this.bottom.SizeHint = this.bottom.Content.GetHeightHint(width);
				minSize.Height += this.bottom.SizeHint.MinimumSize.Height;
				maxSize.Height += this.bottom.SizeHint.MaximumSize.Height;
				prefSize.Height += this.bottom.SizeHint.PreferredSize.Height;
			}
			
			// How much width are preferred for left and right?
			var leftw = 0;
			if ( this.left != null )
			{
				leftw = this.left.SizeHint.PreferredSize.Width;
			}
			var rightw = 0;
			if ( this.left != null )
			{
				rightw = this.left.SizeHint.PreferredSize.Width;				
			}
			
			var overlap = width - prefSize.Width;
			// If there is some width left, we assign it  to the center widget						
			if ( overlap > 0 && this.center != null )
			{
				var w = Math.Max( this.center.sizeHint.MinimumSize.Width, Math.Min( this.center.sizeHint.MaximumSize.Width, witdh - leftw - rightw );
				this.center.GetHeightHint( w );
			}
			else if ( overlap < 0 )
			{
				// Can we fix it by reducing the center widget to minimumSize?
				if ( leftw + rightw + this.center.SizeHint.PreferredSize.Width <= width && this.center != null )
				{
					this.center.GetHeightHint( width - leftw + rightw );
				}
				// We must reduce the size of left and right, too
				else
				{
					var expands = 0;
					leftw = 0;
					if ( this.left != null )
					{
						leftw = this.left.SizeHint.MinimumSize.Width;
						expands++;
					}
					rightw = 0;
					if ( this.left != null )
					{
						rightw = this.left.SizeHint.MinimumSize.Width;							
						expands++;
					}
					var dw = width - this.center.SizeHint.MinimumWidth.Width  + leftw + rightw;
					while( dw > 0 && expands > 0 )
					{
						var part = Math.Floor( dw / expands );
						expands = 0;
						if ( this.left != null )
						{
							var old = leftw;
							leftw = Math.Min( this.left.SizeHint.MaximumSize.Width, leftw + part );
							dw -= (leftw - old);
							if ( this.left.SizeHint.MaximumSize.Width > leftw )
							{
								expands++;
							}
						}
						if ( this.right != null )
						{
							var old = rightw;
							rightw = Math.Min( this.right.SizeHint.MaximumSize.Width, rightw + part );
							dw -= (rightw - old);
							if ( this.right.SizeHint.MaximumSize.Width > rightw )
							{
								expands++;
							}
						}
					}
					
					if ( this.center != null )
					{
						this.center.GetHeightHint( this.center.SizeHint.MinimumWidth.Width + dw );					
					}
				}
			}
			
			var prefh = 0;
			var minh = 0;
			var maxh = 100000;
			if ( this.left != null )
			{
				this.left.SizeHint = this.left.Content.GetHeightHint( leftw );				
				prefh = Math.Max( prefh, this.left.SizeHint.PreferredSize.Height;
				minh = Math.Max( minh, this.left.SizeHint.MinimumSize.Height;
				maxh = Math.Min( maxh, this.left.SizeHint.MaximumSize.Height;
			}
			if ( this.right != null )
			{
				this.right.SizeHint = this.right.Content.GetHeightHint( rightw );
				prefh = Math.Max( prefh, this.right.SizeHint.PreferredSize.Height;
				minh = Math.Max( minh, this.right.SizeHint.MinimumSize.Height;
				maxh = Math.Min( maxh, this.right.SizeHint.MaximumSize.Height;
			}
			if ( this.center != null )
			{
				prefh = Math.Max( prefh, this.center.SizeHint.PreferredSize.Height;
				minh = Math.Max( minh, this.center.SizeHint.MinimumSize.Height;
				maxh = Math.Min( maxh, this.center.SizeHint.MaximumSize.Height;				
			}
			
			minSize.Height += minh;
			maxSize.Height += maxh;
			prefSize.Height += prefh;

			this.sizeHint.MinimumSize = minSize;
			this.sizeHint.MaximumSize = maxSize;
			this.sizeHint.PreferredSize = prefSize;
			
			return this.sizeHint;
		}

		protected override Size Size
		{
			get
			{
				return this.size;
			}
			set
			{
				this.size = value;
				if ( !this.Widget.IsInTree )
				{
					return;
				}

				var dh = this.size.Heigth;
				var toph = 0;
				if ( this.top != null )
				{
					this.top.Size = new Size( this.top.SizeHint.PreferredSize.Width, this.top.SizeHint.PreferredSize.Height );
					toph = this.top.Size.Height;
					this.top.Position = new Point( 0, 0 );
					dh -= this.top.Size;
				}
				if ( this.bottom != null )
				{
					this.bottom.Size = new Size( this.bottom.SizeHint.PreferredSize.Width, this.bottom.SizeHint.PreferredSize.Height );
					dh -= this.bottom.Size;
				}
				
				//  Distribute as much as possible towards the center
				if ( dh >= this.sizeHint.PreferredSize.Height && this.center != null )
				{
					var old = this.center.SizeHint.PreferredSize.Height;
					this.center.Size = new Size( this.center.SizeHint.PreferredSize.Width, Math.Min( old + dh, this.center.SizeHint.MaximumSize.Height) );
					dh -= this.center.Size.Height - old;
				}
				// Shrink the center as much as possible
				else if ( this.center != null )
				{
					var old = this.center.SizeHint.MinimumSize.Height;
					this.center.Size = new Size( this.center.SizeHint.PreferredSize.Width, Math.Max( old + dh, this.center.SizeHint.MinimumSize.Height) );
					dh -= this.center.Size.Height - old;					
				}
				
				// There is still some shrinking to do?
				if ( dh < 0 )
				{
					// How much do we have to distribute (minus the center)?
					dh = this.size.Height;
					if ( this.center != null ) { dh -= this.center.Size.Height };
					
					while( dh > 0 )
					{
						var part = Math.Max( 1, dw / 2 );
						if ( this.left != null )
						{
							var old = leftw;
							leftw = Math.Min( this.left.SizeHint.MaximumSize.Width, leftw + part );
							dw -= (leftw - old);
						}
						if ( this.right != null )
						{
							var old = rightw;
							rightw = Math.Min( this.right.SizeHint.MaximumSize.Width, rightw + part );
							dw -= (rightw - old);
						}
					}
				}
				
				var leftw = 0;				
				if ( this.bottom != null )
				{
					this.bottom.Position = new Point( 0, this.size.Height - this.bottom.Size.Height );
				}
				if ( this.left != null )
				{
					this.left.Position = new Point( 0, toph );
					leftw = this.left.Size.Width;
				}
				if ( this.right != null )
				{
					this.right.Position = new Point( this.size.Width - this.right.Size.Width, toph );
				}
				if ( this.center != null )
				{
					this.center.Position = new Point( leftw, toph );
				}
				
				// Position the elements
				foreach( var item in this.items )
				{
					var pos = new Point( this.position.X + item.Position.X, this.position.Y + item.Position.Y);
					item.Content.SetGeometry( pos, item.Size );
				}
			}
		}
		
		public override Point Position
		{
			get
			{
				return this.position;
			}
			set
			{
				this.SetGeometry( value, this.Size );
			}
		}
		
		public override void SetGeometry( Point pos, Size size)
		{
			this.position = pos;
			this.Size = size;
		}
	}
